#include "Scene.h"

namespace lib3dw
{
	void* pointerToSceneObject;
	Scene::Scene(Camera* newCamera)
	{
		finalShader = new GLSLShaderProgram("shadow", "data/shadow.vert", "data/shadow.frag");
		finalShader->Link();
		camera = newCamera;
		skybox = new SkyBox();
		objectUnderMouse = NULL;
	}

	Scene::~Scene()
	{
		delete finalShader;
		delete skybox;
	}

	void Scene::AddObject(DrawableObject* object)
	{
		drawableObjects.push_back(object);
		SelectableObject* selObject = dynamic_cast <SelectableObject*> (object);
		if(selObject)
		{
			selectableObjects.push_back(selObject);
		}
	}
	
	void Scene::RemoveObject(DrawableObject* object)
	{
		drawableObjects.remove(object);
	}

	void Scene::AddLight(LightSource* light)
	{
		lights.push_back(light);
		switch(lights.size())
		{
			case 1: { light->SetLightNumber(GL_LIGHT0); break; }
			case 2: { light->SetLightNumber(GL_LIGHT1); break; }
			case 3: { light->SetLightNumber(GL_LIGHT2); break; }
			case 4: { light->SetLightNumber(GL_LIGHT3); break; }
			case 5: { light->SetLightNumber(GL_LIGHT4); break; }
			case 6: { light->SetLightNumber(GL_LIGHT5); break; }
			case 7: { light->SetLightNumber(GL_LIGHT6); break; }
			case 8: { light->SetLightNumber(GL_LIGHT7); break; }
		}
		light->InitShadowMap(camera, finalShader, lights.size() - 1);
	}
	
	void Scene::AddHUDElement(HUDElement* element)
	{
		hudElements.push_back(element);
	}

	void Scene::Draw()
	{	
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		if(ConfigSingleton::Instance()->GetWireframe())
		{
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		}
		else
		{
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		}
		Vector3 cameraPosition = camera->GetEye();
		skybox->SetTranslation(cameraPosition);
		skybox->Draw();
		
		for(lightIterator = lights.begin(); lightIterator != lights.end(); ++lightIterator)
		{
			GLenum lightNumber = (*lightIterator)->GetLightNumber();						
			switch((*lightIterator)->GetLightType())
			{
				case SpotLight:
					{
						SpotLightSource* spot = static_cast<SpotLightSource*>(*lightIterator);
						GLfloat cut = spot->GetCutoff();
						GLfloat exp = spot->GetExponent();

						glLightfv(lightNumber, GL_POSITION, spot->GetPosition());
						glLightfv(lightNumber, GL_AMBIENT, spot->GetAmbient());
						glLightfv(lightNumber, GL_DIFFUSE, spot->GetDiffuse());
						glLightfv(lightNumber, GL_SPECULAR, spot->GetSpecular());
						glLightfv(lightNumber, GL_SPOT_DIRECTION, spot->GetDirection());
						glLightfv(lightNumber, GL_SPOT_CUTOFF, &cut);
						glLightfv(lightNumber, GL_SPOT_EXPONENT, &exp);
						break;
					}
				case PointLight:
					{
						PointLightSource* point = static_cast<PointLightSource*>(*lightIterator);
						
						glLightfv(lightNumber, GL_POSITION, point->GetPosition());
						glLightfv(lightNumber, GL_AMBIENT, point->GetAmbient());
						glLightfv(lightNumber, GL_DIFFUSE, point->GetDiffuse());
						glLightfv(lightNumber, GL_SPECULAR, point->GetSpecular());
						break;
					}
			}

			//store a self reference for the function pointer that gets passed to the shadow map generator
			pointerToSceneObject = this;
			glEnable(GL_CULL_FACE);
			glCullFace(GL_FRONT);
			(*lightIterator)->UpdateShadowMap(&StaticWrapperToCallDrawGeometry);
			glDisable(GL_CULL_FACE);						
//			glCullFace(GL_BACK);
			glBindTexture(GL_TEXTURE_2D, (*lightIterator)->GetShadowMapTextureId());
			int textureUnit = (*lightIterator)->GetTextureSlot() + 1;
			GLuint unitId = (*lightIterator)->GetShadowProgramUniformTextureUnitId();
			glUseProgram(finalShader->GetId());
			glUniform1i(unitId, textureUnit);
			glUseProgram(0);
		}
		glUseProgram(finalShader->GetId());		
		DrawGeometry();
		glUseProgram(0);
		
		
		/*

		for(drawableObjectIterator = drawableObjects.begin(); drawableObjectIterator != drawableObjects.end(); ++drawableObjectIterator)
		{
			const std::vector<GLuint>* shaderPasses = (*drawableObjectIterator)->GetModel()->GetShaderPasses();
			std::vector<GLuint>::const_iterator shaderPassesIterator;
			int k = 0;
			for(shaderPassesIterator = shaderPasses->begin(); shaderPassesIterator != shaderPasses->end(); ++shaderPassesIterator)
			{ 	
				GLuint shaderPass = *shaderPassesIterator;
				// if this is not our first pass, enable blending
				if(k!=0)
				{
					glBlendFunc(GL_ONE, GL_ONE);
					glEnable(GL_BLEND);
				}

				//HACK
				SelectableObject* selObject = dynamic_cast <SelectableObject*> ((*drawableObjectIterator));
				if(selObject && ConfigSingleton::Instance()->GetFrustumCulling())
				{
					Box* box = selObject->GetBoundingBox();
					const Vector3* translation = (*drawableObjectIterator)->GetModel()->GetTranslation();
					Vector3 t = Vector3(translation->x, translation->y, translation->z);
					if(frustum->Test(box))
					{
						glUseProgram(shaderPass);
						(*drawableObjectIterator)->Draw();
						glUseProgram(0);
					}
				}
				// if our object dont't have bounding boxes
				// we need to render them
				else
				{
						glUseProgram(shaderPass);
						(*drawableObjectIterator)->Draw();
						glUseProgram(0);
				}
				glDisable(GL_BLEND);
				k++;
			}
		}*/

		for(selectableObjectIterator = selectableObjects.begin(); selectableObjectIterator != selectableObjects.end(); ++selectableObjectIterator)
		{
			int test = selectableObjects.size();
			if((*selectableObjectIterator)->Intersect(pickRay, -100.0, 100.0))
			{
				objectUnderMouse = (*selectableObjectIterator);
			}
		}
		
		for(hudElementIterator = hudElements.begin(); hudElementIterator != hudElements.end(); ++hudElementIterator)
		{
			(*hudElementIterator)->Draw();
		}
	}
	
	void Scene::DrawGeometry()
	{
		for(drawableObjectIterator = drawableObjects.begin(); drawableObjectIterator != drawableObjects.end(); ++drawableObjectIterator)
		{
		

			DrawableObject* currentObject = (*drawableObjectIterator);
				if(ConfigSingleton::Instance()->GetFrustumCulling())
				{
					Frustum* frustum = camera->GetFrustum();
					Box box = currentObject->GetModel()->GetBoundingBox();
					if(frustum->Test(&box))
					{
						(*drawableObjectIterator)->Draw();
					}
				}
				// if our object dont't have bounding boxes
				// we need to render them
				else
				{
					currentObject->Draw();
				}
		}
	} 
	
	SelectableObject* Scene::GetObjectUnderMouse(PickRay* newPickRay)
	{
		pickRay = newPickRay;
		return objectUnderMouse;
	}
	
	// static wrapper: allows LightSource:: to call the render function to generate shadow maps
   void Scene::StaticWrapperToCallDrawGeometry()
   {
       Scene* mySelf = (Scene*) pointerToSceneObject;
       mySelf->DrawGeometry();
   }
}